﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RapidRepository.Cache;
using RapidRepository.Context;
using RapidRepository.View;
namespace RapidRepository
{
    /// <summary>
    /// The RapidContext tracks usage of the repository and manages access to the document storage.
    /// </summary>
    public class RapidContext
    { 
        #region Declarations

        static object syncRoot = new object();
        static RapidContext context = null;        
        IFileManager fileManager;
        ISerialiser serialiser;
        List<OperationRequest> operationRequests = new List<OperationRequest>();
        internal static List<IRapidViewDataInternal> rapidViews = new List<IRapidViewDataInternal>();
        List<EntitiesLoaded> entitiesLoaded = new List<EntitiesLoaded>();

        private class RapidViewKey
        {
            public IRapidViewDataInternal RapidViewData { get; set; }
            public ViewKey ViewKey { get; set; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RapidContext"/> class.
        /// </summary>
        internal RapidContext() : this(new FileManager(), new Serialiser()) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RapidContext"/> class.
        /// </summary>
        /// <param name="fileManager">The file manager.</param>
        /// <param name="serialiser">The serialiser.</param>
        internal RapidContext(IFileManager fileManager, ISerialiser serialiser)
        {
            Contract.Requires("fileManager", fileManager != null);
            Contract.Requires("serialiser", serialiser != null);

            this.fileManager = fileManager;
            this.serialiser = serialiser;
        }

        #endregion

        #region CurrentContext

        /// <summary>
        /// Gets the current context.
        /// </summary>
        /// <value>The current context.</value>
        public static RapidContext CurrentContext
        {
            get
            {
                if (context == null)
                {
                    lock (syncRoot)
                    {
                        if (context == null)
                        {
                            context = new RapidContext();
                        }
                    }
                }
                return context;
            }
        }

        #endregion
                
        #region SaveChanges
        
        /// <summary>
        /// Saves the changes.
        /// </summary>
        public void SaveChanges()
        {
            List<ViewKey> rapidViews = new List<ViewKey>();

            foreach (OperationRequest request in this.operationRequests)
            {
                switch (request.OperationRequestType)
                {
                    case OperationRequestType.Add:
                        if (((IRapidEntity)request.Entity).Id == Guid.Empty)
                        {
                            ((IRapidEntity)request.Entity).Id = Guid.NewGuid();
                        }
                        var addString = this.serialiser.Serialise(request.EntityType, request.Entity);
                        this.fileManager.Save(request.EntityType, ((IRapidEntity)request.Entity).Id, addString);
                        AddToCache(request.EntityType, (IRapidEntity)request.Entity);
                        AddToRapidViewsCollectionForSave(rapidViews, AddToView(request));
                        break;
                    case OperationRequestType.Update:
                        var updateString = this.serialiser.Serialise(request.EntityType, request.Entity);
                        this.fileManager.Save(request.EntityType, request.EntityId, updateString);
                        EntityCache.Update(request.EntityType, request.EntityId, request.Entity);
                        AddToRapidViewsCollectionForSave(rapidViews, UpdateToView(request));
                        break;
                    case OperationRequestType.Delete:
                        this.fileManager.Delete(request.EntityType, request.EntityId);
                        EntityCache.Remove(request.EntityType, request.EntityId);
                        AddToRapidViewsCollectionForSave(rapidViews, DeleteToView(request));
                        break;
                }
            }

            SaveViews(rapidViews);

            DeleteAllOperationRequests();
        }

        #endregion

        #region GetOperationRequests

        /// <summary>
        /// Gets the operation requests.
        /// </summary>
        /// <value>The operation requests.</value>
        public List<OperationRequest> OperationRequests
        {
            get
            {
                lock (syncRoot)
                {
                    return this.operationRequests;
                }
            }
        }

        #endregion

        #region AddOperationRequest

        /// <summary>
        /// Adds the operation request.
        /// </summary>
        /// <param name="operationRequest">The operation request.</param>
        internal void AddOperationRequest(OperationRequest operationRequest)
        {
            Contract.Requires("operationRequest", operationRequest != null);

            this.operationRequests.Add(operationRequest);
        }

        #endregion

        #region DeleteOperationRequest

        /// <summary>
        /// Deletes the operation request.
        /// </summary>
        /// <param name="operationRequest">The operation request.</param>
        public void DeleteOperationRequest(OperationRequest operationRequest)
        {
            Contract.Requires("operationRequest", operationRequest != null);

            this.operationRequests.Remove(operationRequest);
        } 

        #endregion

        #region DeleteAllOperationRequests

        /// <summary>
        /// Deletes all operation requests.
        /// </summary>
        public void DeleteAllOperationRequests()
        {
            this.operationRequests.Clear();
        } 

        #endregion

        #region Exists

        /// <summary>
        /// Returns true if the entity exists.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="id">The id.</param>
        internal bool Exists<TEntity>(Guid id)
        {
            Contract.Requires("id", id != Guid.Empty);

            var cachedEntity = EntityCache.GetById<TEntity>(id);

            if (cachedEntity != null)
            {
                return true;
            }

            return this.fileManager.Exists<TEntity>(id);
        }

        #endregion

        #region GetById

        /// <summary>
        /// Gets the entity by id.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="id">The id.</param>
        internal TEntity GetById<TEntity>(Guid id)
        {
            Contract.Requires("id", id != Guid.Empty);

            var cachedEntity = EntityCache.GetById<TEntity>(id);

            if (cachedEntity != null)
            {
                return cachedEntity;
            }

            string fileContent = this.fileManager.GetFile<TEntity>(id);

            var entity = (IRapidEntity)this.serialiser.Deserialise<TEntity>(fileContent);

            AddToCache(typeof(TEntity), entity);

            return (TEntity)entity;
        }

        #endregion

        #region GetAll

        /// <summary>
        /// Gets a list of all entities of the specified type.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        internal IList<TEntity> GetAll<TEntity>()
        {
            lock (syncRoot)
            {
                if (entitiesLoaded.Any(x => x.EntityType == typeof(TEntity)))
                {
                    var cachedEntities = EntityCache.GetAll<TEntity>();

                    if (cachedEntities != null && cachedEntities.Count > 0)
                    {
                        return cachedEntities;
                    }
                }
            }

            string[] entityList = this.fileManager.LoadFiles<TEntity>();

            IList<TEntity> entities = new List<TEntity>();

            foreach (var entityContent in entityList)
            {
                var entity = this.serialiser.Deserialise<TEntity>(entityContent);
                entities.Add(entity);

                AddToCache(typeof(TEntity), (IRapidEntity)entity);
            }

            SetEntityLoaded<TEntity>();
            
            return entities;
        }

        internal void SetEntityLoaded<TEntity>()
        {
            lock (syncRoot)
            {
                entitiesLoaded.Add(new EntitiesLoaded { EntityType = typeof(TEntity) });
            }
        }

        #endregion

        #region AddView

        /// <summary>
        /// Adds a rapid view.
        /// </summary>
        /// <param name="view">The view.</param>
        internal static void AddRapidView(IRapidViewDataInternal view)
        {
            lock (syncRoot)
            {
                if (!rapidViews.Any(x => x.ViewType == view.ViewType))
                {
                    rapidViews.Add(view);
                }
            }
        }

        #endregion

        #region SaveView

        private void SaveViews(List<ViewKey> viewsToSave)
        {
            foreach (ViewKey viewToSave in (List<ViewKey>)viewsToSave)
            {
                IRapidViewDataInternal rapidViewData = LoadRapidViewData(viewToSave);

                RapidViewKey rapidViewKey = new RapidViewKey 
                {
                    RapidViewData = rapidViewData,
                    ViewKey = viewToSave
                };

                if (!rapidViewData.IsLoaded)
                {
                    LoadView(rapidViewData.EntityType, rapidViewData.ViewType);
                }
                SaveView(rapidViewKey);                
            }
        }

        private void SaveView(object rapidViewKey)
        {
            RapidViewKey rapidViewKeyData = (RapidViewKey)rapidViewKey;

            System.Collections.IList views = (System.Collections.IList)rapidViewKeyData.RapidViewData.LoadView();

            StringBuilder serialisedView = new StringBuilder();

            foreach (var view in views)
            {
                serialisedView.AppendLine(this.serialiser.Serialise(rapidViewKeyData.ViewKey.ViewType, view));
            }
                
            this.fileManager.SaveViewFile(rapidViewKeyData.ViewKey.ViewType, serialisedView.ToString());
            
        }

        #endregion

        #region LoadView

        /// <summary>
        /// Loads the view.
        /// </summary>
        /// <typeparam name="TView">The type of the view.</typeparam>
        internal List<TView> LoadView<TView>(Type entityType, Type viewType)
        {
            IRapidViewDataInternal rapidView = LoadRapidViewData(new ViewKey { EntityType = entityType, ViewType = viewType });

            if (!rapidView.IsLoaded)            
            {
                var viewFile = this.fileManager.GetViewFile(viewType);
                if (viewFile == null)
                {
                    return new List<TView>();
                }

                List<string> splitView = new List<string>(viewFile.Split(new string[] { Environment.NewLine }, new StringSplitOptions()));

                List<object> rapidViews = new List<object>();

                foreach (var item in splitView)
                {
                    if (!string.IsNullOrEmpty(item))
                    {
                        rapidViews.Add((IRapidView)this.serialiser.Deserialise(viewType, item));
                    }
                }
                rapidView.InsertView(rapidViews);
                rapidView.IsLoaded = true;    
            }

            var views= rapidView.LoadView();
            List<TView> viewsToReturn = new List<TView>();
            foreach (var view in views)
            {
                viewsToReturn.Add((TView)view);
            }
            return viewsToReturn;
        }

        /// <summary>
        /// Loads the view.
        /// </summary>
        /// <typeparam name="TView">The type of the view.</typeparam>
        internal void LoadView(Type entityType, Type viewType)
        {
            IRapidViewDataInternal rapidView = LoadRapidViewData(new ViewKey { EntityType = entityType, ViewType = viewType });

            if (!rapidView.IsLoaded)
            {
                var viewFile = this.fileManager.GetViewFile(viewType);
                if (viewFile == null)
                {
                    return;
                }

                List<string> splitView = new List<string>(viewFile.Split(new string[] { Environment.NewLine }, new StringSplitOptions()));


                List<object> rapidViews = new List<object>();

                foreach (var item in splitView)
                {
                    if (!string.IsNullOrEmpty(item))
                    {
                        rapidViews.Add((IRapidView)this.serialiser.Deserialise(viewType, item));
                    }
                }
                rapidView.InsertView(rapidViews);
                rapidView.IsLoaded = true;
            }
        }

        #endregion

        #region SynchroniseView

        internal static void SynchroniseView<TEntity, TView>()
            where TEntity : IRapidEntity
            where TView : IRapidView
        {
            IList<TEntity> entities = CurrentContext.GetAll<TEntity>();
            ViewKey viewKey = new ViewKey { EntityType = typeof(TEntity), ViewType = typeof(TView)};           
            IRapidViewDataInternal rapidView = LoadRapidViewData(viewKey);
            RapidViewKey rapidViewKey = new RapidViewKey
            {
                ViewKey = viewKey,
                RapidViewData = rapidView
            };

            rapidView.InsertView(new List<object>());
            foreach (var entity in entities)
            {
                rapidView.Add(entity);
            }
            CurrentContext.SaveView(rapidViewKey);
        }

        #endregion

        #region Private Methods

        private static void AddToCache(Type entityType, IRapidEntity entity)
        {
            EntityCache.Add(entityType, ((IRapidEntity)entity).Id, entity);            
        }

        private static IRapidViewDataInternal LoadRapidViewData(ViewKey viewKey)
        {
            var rapidView = rapidViews.Where(x => x.ViewType == viewKey.ViewType).FirstOrDefault();
            if (rapidView == null)
            {
                throw new InvalidOperationException(string.Format("The view type {0} has not been registered", viewKey.ViewType.FullName));
            }
            return rapidView;
        }

        private List<ViewKey> AddToView(OperationRequest request)
        {
            List<ViewKey> viewTypesForSaving = new List<ViewKey>();
            List<IRapidViewDataInternal> views = rapidViews.Where(x => x.EntityType == request.EntityType).ToList();

            foreach (var view in views)
            {
                if (!view.IsLoaded)
                {
                    LoadView(view.EntityType, view.ViewType);
                }
                view.Add(request.Entity);
                viewTypesForSaving.Add(new ViewKey { EntityType = request.EntityType, ViewType = view.ViewType });
            }

            return viewTypesForSaving;
        }

        private List<ViewKey> UpdateToView(OperationRequest request)
        {
            List<ViewKey> viewTypesForSaving = new List<ViewKey>();
            List<IRapidViewDataInternal> views = rapidViews.Where(x => x.EntityType == request.EntityType).ToList();

            foreach (var view in views)
            {
                if (!view.IsLoaded)
                {
                    LoadView(view.EntityType, view.ViewType);
                }

                view.Update(request.Entity);
                viewTypesForSaving.Add(new ViewKey { EntityType = request.EntityType, ViewType = view.ViewType });
            }

            return viewTypesForSaving;
        }

        private List<ViewKey> DeleteToView(OperationRequest request)
        {
            List<ViewKey> viewTypesForSaving = new List<ViewKey>();
            List<IRapidViewDataInternal> views = rapidViews.Where(x => x.EntityType == request.EntityType).ToList();

            foreach (var view in views)
            {
                if (!view.IsLoaded)
                {
                    LoadView(view.EntityType, view.ViewType);
                }
                view.Delete(request.EntityId);
                viewTypesForSaving.Add(new ViewKey { EntityType = request.EntityType, ViewType = view.ViewType });
            }

            return viewTypesForSaving;
        }

        private static void AddToRapidViewsCollectionForSave(List<ViewKey> rapidViews, List<ViewKey> viewsToSave)
        {
            foreach (var viewKey in viewsToSave)
            {
                if (!rapidViews.Any(x => x.EntityType == viewKey.EntityType && x.ViewType == viewKey.ViewType))
                {
                    rapidViews.Add(viewKey);
                }
            }
        }

        #endregion
    }
}
